﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using ESRI.ArcGIS.ADF.Serialization;
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.DataSourcesRaster;
using ESRI.ArcGIS.Desktop.AddIns;
using ESRI.ArcGIS.Framework;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.esriSystem;

namespace BesAsm.Emgaats.Maintenance
{
    public class EmgaatsMaintenanceExtension : ESRI.ArcGIS.Desktop.AddIns.Extension
    {
        private static EmgaatsMaintenanceExtension _extension;
        private LinkToolSettings _linkToolSettings = null;
        private IActiveViewEvents_Event _activeView = null;
        private Configuration _config = null;
        private int? _dmeSelectionCount = null;
        private int? _linkSelectionCount = null;

        public EmgaatsMaintenanceExtension()
        {
            _extension = this;
        }

        /// <summary>
        /// A reference to the instantiated Emgaats Editing Extension.
        /// </summary>
        internal static EmgaatsMaintenanceExtension Extension
        {
            get { return _extension; }
        }

        internal static void RunTool(Geoprocessor geoprocessor, IGPProcess process, ITrackCancel TC)
        {
            // Set the overwrite output option to true
            geoprocessor.OverwriteOutput = true;

            // Execute the tool            
            try
            {
                geoprocessor.Execute(process, TC);

            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Link tool setup information.
        /// </summary>
        internal LinkToolSettings Settings
        {
            get
            {
                if (_linkToolSettings == null)
                {
                    _linkToolSettings = new LinkToolSettings();
                }
                return _linkToolSettings;
            }
        }

        internal Configuration Configuration
        {
            get
            {
                if (_config == null)
                {
                    _config = new Configuration();
                }

                return _config;
            }
        }

        internal int DmeSelectionCount
        {
            get
            {
                if (_dmeSelectionCount.HasValue)
                {
                    return _dmeSelectionCount.Value;
                }
                else
                {
                    IFeatureClass dmeClass = this.Configuration.DmeLinksClass;
                    IFeatureLayer[] dmeLayers = this.FindFeatureLayers(dmeClass);

                    int selectionCount = 0;
                    foreach (IFeatureLayer dmeLayer in dmeLayers)
                    {
                        IFeatureSelection selection = dmeLayer as IFeatureSelection;
                        {
                            if (selection != null)
                                selectionCount += selection.SelectionSet.Count;
                        }
                    }

                    _dmeSelectionCount = selectionCount;
                    return selectionCount;
                }
            }
        }

        internal int LinkSelectionCount
        {
            get
            {
                if (_linkSelectionCount.HasValue)
                {
                    return _linkSelectionCount.Value;
                }
                else
                {
                    int selectionCount = 0;

                    IFeatureClass linkClass = this.Configuration.MasterLinksEditClass;
                    IFeatureLayer linkLayer = EmgaatsMaintenanceExtension.Extension.FindFeatureLayer(linkClass);
                    IFeatureSelection selection = linkLayer as IFeatureSelection;
                    if (selection != null)
                    {
                        selectionCount = selection.SelectionSet.Count;
                    }

                    _linkSelectionCount = selectionCount;
                    return selectionCount;
                }
            }
        }

        /// <summary>
        /// Gets the full path name of the current map document
        /// </summary>
        internal string MxDocFileName
        {
            get
            {
                ITemplates appTemplates = ArcMap.Application.Templates;
                return appTemplates.get_Item(appTemplates.Count - 1);
            }
        }

        internal static void AddReference(object comObject)
        {
            if (comObject != null && Marshal.IsComObject(comObject))
            {
                IntPtr ptr = Marshal.GetIUnknownForObject(comObject);
                object obj = Marshal.GetObjectForIUnknown(ptr);
            }
        }

        internal static int ReleaseComObject(object comObject)
        {
            if (comObject != null && Marshal.IsComObject(comObject))
            {
                return Marshal.ReleaseComObject(comObject);
            }
            else
            {
                return 0;
            }
        }

        internal IFeatureLayer FindFeatureLayer(string dataSourcePath)
        {
            foreach (ILayer layer in _GetLayers())
            {
                IFeatureLayer featureLayer = layer as IFeatureLayer;
                if (featureLayer != null && featureLayer.FeatureClass != null)
                {
                    string layerPath = ((IDataset)featureLayer.FeatureClass).Workspace.PathName;
                    layerPath += "\\" + ((IDataset)featureLayer.FeatureClass).Name;

                    // We check the start because the layerPath will not include a '*.shp" extension, for example
                    if(dataSourcePath.ToLowerInvariant().StartsWith(layerPath.ToLowerInvariant()))
                        return featureLayer;
                }
            }
            return null;
        }

        internal IFeatureLayer FindFeatureLayer(IFeatureClass featureClass)
        {
            if (featureClass == null)
                return null;

            foreach (ILayer layer in _GetLayers())
            {
                IFeatureLayer featureLayer = layer as IFeatureLayer;
                if (featureLayer != null && featureLayer.FeatureClass != null)
                {
                    if (featureLayer.FeatureClass == featureClass)
                        return featureLayer;
                }
            }
            return null;
        }

        internal IFeatureLayer[] FindFeatureLayers(IFeatureClass featureClass)
        {
            List<IFeatureLayer> featureLayers = new List<IFeatureLayer>();

            if (featureClass != null)
            {
                foreach (ILayer layer in _GetLayers())
                {
                    IFeatureLayer featureLayer = layer as IFeatureLayer;
                    if (featureLayer != null && featureLayer.FeatureClass != null)
                    {
                        if (featureLayer.FeatureClass == featureClass)
                            featureLayers.Add(featureLayer);
                    }
                }
            }

            return featureLayers.ToArray();
        }

        internal IRasterLayer FindRasterLayer(IRaster raster)
        {
            if (raster == null)
                return null;

            foreach (ILayer layer in _GetLayers())
            {
                IRasterLayer rasterLayer = layer as IRasterLayer;
                if (rasterLayer != null && rasterLayer.Raster != null)
                {
                    // Since there can be multiple instances of the raster created from a raster dataset
                    // we check for equivalency by looking at the "complete name" of each raster's underlying
                    // dataset. Note that the rasters could have different properties.

                    string layerPath = ((IRasterAnalysisProps)rasterLayer.Raster).RasterDataset.CompleteName;
                    string rasterPath = ((IRasterAnalysisProps)raster).RasterDataset.CompleteName;
                    if (string.Compare(layerPath, rasterPath, true) == 0)
                        return rasterLayer;
                }
            }
            return null;
        }

        public static double ManningsFlowCircular(double roughness, double circleRadius, double slope, bool usUnits)
        {
            double A = Math.PI * circleRadius * circleRadius;
            double P = 2.0 * Math.PI * circleRadius;
            double Rh = A / P;
            return ManningsFlow(roughness, Rh, slope, usUnits);
        }

        public static double ManningsFlow(double roughness, double hydraulicRadius, double slope, bool usUnits)
        {
            if (usUnits)
                return ManningsFlow(roughness, hydraulicRadius, slope, 1.4859);
            else
                return ManningsFlow(roughness, hydraulicRadius, slope, 1.0);
        }

        public static double ManningsFlow(double roughness, double hydraulicRadius, double slope, double unitConversion)
        {
            return (unitConversion / roughness) * Math.Pow(hydraulicRadius, 2.0 / 3.0) * Math.Sqrt(slope);
        }

        protected override void OnStartup()
        {
            ArcMap.Events.NewDocument += new IDocumentEvents_NewDocumentEventHandler(_DocumentEvents_NewDocument);
            ArcMap.Events.OpenDocument += new IDocumentEvents_OpenDocumentEventHandler(_DocumentEvents_OpenDocument);
            ArcMap.Events.CloseDocument += new IDocumentEvents_CloseDocumentEventHandler(_DocumentEvents_CloseDocument);
            ArcMap.Events.ActiveViewChanged += new IDocumentEvents_ActiveViewChangedEventHandler(_Events_ActiveViewChanged);
        }

        protected override void OnShutdown()
        {
            if (_linkToolSettings != null)
            {
                _linkToolSettings.Dispose();
                _linkToolSettings = null;
            }

            ArcMap.Events.NewDocument -= new IDocumentEvents_NewDocumentEventHandler(_DocumentEvents_NewDocument);
            ArcMap.Events.OpenDocument -= new IDocumentEvents_OpenDocumentEventHandler(_DocumentEvents_OpenDocument);
            ArcMap.Events.CloseDocument -= new IDocumentEvents_CloseDocumentEventHandler(_DocumentEvents_CloseDocument);
            ArcMap.Events.ActiveViewChanged -= new IDocumentEvents_ActiveViewChangedEventHandler(_Events_ActiveViewChanged);
        }

        public void CheckEditLayers(out int totalErrors, out int totalWarnings)
        {
            totalErrors = 0;
            totalWarnings = 0;

            int errorCount, warningCount;
            _CheckAppendClass(out errorCount, out warningCount);

            totalErrors += errorCount;
            totalWarnings += warningCount;

            _CheckArchiveClass(out errorCount, out warningCount);

            totalErrors += errorCount;
            totalWarnings += warningCount;
        }

        private void _CheckAppendClass(out int errorCount, out int warningCount)
        {
            IFeatureClass appendClass = this.Settings.AppendClass;
            int errorField = appendClass.FindField("ErrorCode");
            if (errorField < 0)
            {
                errorField = EmgaatsMaintenanceExtension.AddField(appendClass, "ErrorCode", esriFieldType.esriFieldTypeInteger);
            }

            int errorTextField = appendClass.FindField("ErrorText");
            if (errorTextField < 0)
            {
                errorTextField = EmgaatsMaintenanceExtension.AddField(appendClass, "ErrorText", esriFieldType.esriFieldTypeString, 100);
            }

            errorCount = 0;
            warningCount = 0;

            bool operationStarted = false;
            IWorkspaceEdit2 editWorkspace = ((IDataset)appendClass).Workspace as IWorkspaceEdit2;
            if (editWorkspace.IsBeingEdited() && !editWorkspace.IsInEditOperation)
            {
                editWorkspace.StartEditOperation();
                operationStarted = true;
            }
            try
            {
                IFeatureCursor cursor = appendClass.Update(null, false);
                try
                {
                    IFeature link = cursor.NextFeature();
                    while (link != null)
                    {
                        try
                        {
                            string errorText;
                            LinkErrorCodes error = _CheckLinkForAppend(link, out errorText);

                            bool update = false;
                            if (error.ToString().StartsWith("Error"))
                            {
                                errorCount++;
                                errorText = "Error: " + errorText;
                            }
                            else if (error.ToString().StartsWith("Warning"))
                            {
                                warningCount++;
                                errorText = "Warning: " + errorText;
                                update = true;
                            }
                            else
                            {
                                update = true;
                            }

                            link.set_Value(errorField, error);
                            link.set_Value(errorTextField, errorText);

                            if (update)
                                this.UpdateLinkAttributes(link);

                            cursor.UpdateFeature(link);
                        }
                        finally
                        {
                            EmgaatsMaintenanceExtension.ReleaseComObject(link);
                        }

                        link = cursor.NextFeature();
                    }
                }
                finally
                {
                    EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
                }

                if (operationStarted)
                    editWorkspace.StopEditOperation();
            }
            catch
            {
                if (operationStarted)
                    editWorkspace.AbortEditOperation();
                throw;
            }
        }

        private void _CheckArchiveClass(out int errorCount, out int warningCount)
        {
            IFeatureClass archiveClass = this.Settings.ArchiveClass;
            int errorField = archiveClass.FindField("ErrorCode");
            if (errorField < 0)
            {
                errorField = EmgaatsMaintenanceExtension.AddField(archiveClass, "ErrorCode", esriFieldType.esriFieldTypeInteger);
            }

            int errorTextField = archiveClass.FindField("ErrorText");
            if (errorTextField < 0)
            {
                errorTextField = EmgaatsMaintenanceExtension.AddField(archiveClass, "ErrorText", esriFieldType.esriFieldTypeString, 100);
            }

            errorCount = 0;
            warningCount = 0;

            IFeatureCursor cursor = archiveClass.Update(null, false);
            try
            {
                IFeature link = cursor.NextFeature();
                while (link != null)
                {
                    try
                    {
                        string errorText;
                        LinkErrorCodes error = _CheckLinkForArchive(link, out errorText);

                        if (error.ToString().StartsWith("Error"))
                        {
                            errorCount++;
                            errorText = "Error: " + errorText;
                        }
                        else if (error.ToString().StartsWith("Warning"))
                        {
                            warningCount++;
                            errorText = "Warning: " + errorText;
                        }
                        else
                        {
                            errorText = error.ToString();
                        }

                        link.set_Value(errorField, error);
                        link.set_Value(errorTextField, errorText);

                        cursor.UpdateFeature(link);
                    }
                    finally
                    {
                        EmgaatsMaintenanceExtension.ReleaseComObject(link);
                    }

                    link = cursor.NextFeature();
                }
            }
            finally
            {
                EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
            }
        }

        private LinkErrorCodes _CheckLinkForAppend(IFeature link, out string errorText)
        {
            // Check for errors
            LinkErrorCodes error = LinkErrorCodes.None;
            errorText = string.Empty;

            // Check geometry
            esriNonSimpleReasonEnum nonSimpleReason;
            if (link.Shape.IsEmpty)
            {
                error = LinkErrorCodes.ErrorGeometryEmpty;
                errorText = "Geometry is empty.";
            }
            else if (!_IsShapeValid(link.Shape, out nonSimpleReason))
            {
                error = LinkErrorCodes.ErrorGeometryNotSimple;
                errorText = string.Format("Geometry has topological error. ({0})", nonSimpleReason.ToString());
            }
            else if (_IsShapeMultiPart(link.Shape))
            {
                error = LinkErrorCodes.ErrorGeometryMultiPart;
                errorText = "Geometry is a multipart polyline.";
            }

            // Check attributes
            if (error == LinkErrorCodes.None)
            {
                for (int f = 0; f < link.Fields.FieldCount; f++)
                {
                    IField field = link.Fields.get_Field(f);
                    if (!_ValidateField(link, field.Name))
                    {
                        error = LinkErrorCodes.ErrorAttributeInvalid;
                        errorText = string.Format("The value for field '{0}' is invalid.", field.Name);
                    }
                }
            }

            //Check node connections
            if(error == LinkErrorCodes.None)
            {
                error = _CheckNode(link, false, out errorText);
                if (error == LinkErrorCodes.None)
                    error = _CheckNode(link, true, out errorText);

                if (error == LinkErrorCodes.None)
                    error = _CheckForDuplicateLinks(link, out errorText);
            }

            if (string.IsNullOrEmpty(errorText))
                errorText = error.ToString();

            return error;
        }

        private LinkErrorCodes _CheckForDuplicateLinks(IFeature link, out string errorText)
        {
            LinkErrorCodes error = LinkErrorCodes.None;
            errorText = string.Empty;

            int upNodeNameField = link.Class.FindField("us_node_name_deprecated");
            int downNodeNameField = link.Class.FindField("ds_node_name_deprecated");

            string upName = link.get_Value(upNodeNameField) as string;
            string downName = link.get_Value(downNodeNameField) as string;
            if (!string.IsNullOrEmpty(upName) && !string.IsNullOrEmpty(downName))
            {
                IFeatureClass masterLinkClass = EmgaatsMaintenanceExtension.Extension.Configuration.MasterLinksEditClass;
                IQueryFilter query = new QueryFilterClass();
                query.WhereClause = string.Format("us_node_name_deprecated = '{0}' AND ds_node_name_deprecated = '{1}'", upName, downName);

                if (masterLinkClass.FeatureCount(query) > 0)
                {
                    error = LinkErrorCodes.WarningSimilarLinkExists;
                    errorText = "Another link exists with the same end nodes.";
                }
            }

            return error;
        }

        private LinkErrorCodes _CheckLinkForArchive(IFeature link, out string errorText)
        {
            LinkErrorCodes error = LinkErrorCodes.None;
            errorText = LinkErrorCodes.None.ToString();

            int linkId = (int)link.get_Value(link.Fields.FindField("link_id"));

            IQueryFilter filter = new QueryFilterClass();
            filter.WhereClause = string.Format("status = 'ACT' AND link_id = {0}", linkId);
            IFeatureCursor cursor = this.Configuration.MasterLinksEditClass.Search(filter, false);
            try
            {
                IFeature masterLink = cursor.NextFeature();
                if (masterLink == null)
                {
                    errorText = "Link Id does not exist in the Master Links database.";
                    error = LinkErrorCodes.WarningLinkIdDoesNotExist;
                }
                else
                    EmgaatsMaintenanceExtension.ReleaseComObject(masterLink);
            }
            finally
            {
                EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
            }

            return error;
        }

        internal void UpdateLinkAttributes(IFeature link)
        {
            FieldMapping fieldMapping = this.Configuration.LinkFieldMapping;

            for (int f = 0; f < link.Fields.FieldCount; f++)
            {
                IField toField = link.Fields.get_Field(f);
                if (toField.Type != esriFieldType.esriFieldTypeOID && toField.Type != esriFieldType.esriFieldTypeGeometry && toField.Editable)
                {
                    if (fieldMapping.Contains(toField.Name) && fieldMapping[toField.Name].AutoUpdate && !fieldMapping[toField.Name].Ignore)
                    {
                        fieldMapping.UpdateValueForField(toField.Name, link);
                    }
                }
            }
        }

        private bool _IsShapeValid(IGeometry shape, out esriNonSimpleReasonEnum reason)
        {
            ITopologicalOperator3 topoOp = shape as ITopologicalOperator3;
            bool isSimple = topoOp.get_IsSimpleEx(out reason);
            return isSimple;
        }

        private bool _IsShapeMultiPart(IGeometry shape)
        {
            return ((IGeometryCollection)shape).GeometryCount > 1;
        }

        private bool _ValidateField(IFeature link, string fieldName)
        {
            bool valid = true;
            if (this.Configuration.LinkFieldMapping.Contains(fieldName) && this.Configuration.LinkFieldMapping[fieldName].Validate)
            {
                int fieldIndex = link.Fields.FindField(fieldName);
                if (fieldIndex > -1)
                {
                    object dbValue = link.get_Value(fieldIndex);
                    valid = this.Configuration.LinkFieldMapping[fieldName].IsValidValue(dbValue);
                }
            }
            return valid;
        }

        private LinkErrorCodes _CheckNode(IFeature link, bool upstream, out string errorText)
        {
            errorText = string.Empty;
            string nodePostion = upstream ? "Upstream" : "Downstream";
            string nodeName = link.get_Value(link.Fields.FindField(upstream ? "us_node_name_deprecated" : "ds_node_name_deprecated")) as string;

            if (string.IsNullOrEmpty(nodeName))
            {
                errorText = nodePostion + " node name is empty.";
                return upstream ? LinkErrorCodes.ErrorUpstreamNodeNameEmpty : LinkErrorCodes.ErrorDownstreamNodeNameEmpty;
            }
            else
            {
                int existingNodeId = GetNodeId(nodeName);
                bool nodeExists = existingNodeId > -1;

                IFeatureClass nodeClass = this.Configuration.MasterNodesEditClass;
                int nodeIdField = nodeClass.FindField("node_id");

                IPolyline line = link.Shape as IPolyline;
                IPoint endPoint = upstream ? line.FromPoint : line.ToPoint;
                IEnvelope snapRegion = endPoint.Envelope;
                snapRegion.Expand(0.1, 0.1, false);

                ISpatialFilter filter = new SpatialFilterClass();
                filter.Geometry = snapRegion;
                filter.GeometryField = nodeClass.ShapeFieldName;
                filter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
                filter.WhereClause = "status = 'ACT'";

                bool nodeColocated = false;
                bool existingColocated = false;
                IFeatureCursor cursor = nodeClass.Search(filter, false);
                try
                {
                    IFeature node = cursor.NextFeature();
                    try
                    {
                        if (node != null)
                        {
                            nodeColocated = true;
                            int nodeId = (int)node.get_Value(nodeIdField);
                            if (nodeId == existingNodeId)
                            {
                                existingColocated = true;
                            }
                        }
                    }
                    finally
                    {
                        EmgaatsMaintenanceExtension.ReleaseComObject(node);
                    }
                }
                finally
                {
                    EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
                }

                // If there is a node with the same name and location (existingColocated = true), then there is no error. Otherwise;
                if (!existingColocated)
                {
                    if (nodeExists || nodeColocated)
                    {
                        if (nodeExists)
                        {
                            errorText = nodePostion + " node with the given name is not located at the end of the link.";
                        }
                        else
                        {
                            errorText = nodePostion + " node has a different name than specified in the append table.";
                        }

                        // If the node exists (elsewhere) or there is another node located here, then there is a mismatch
                        return upstream ? LinkErrorCodes.ErrorUpstreamNodeMismatch : LinkErrorCodes.ErrorDownstreamNodeMismatch;
                    }
                    else
                    {
                        errorText = nodePostion + " node is missing and will be created when link is committed.";
                        // Otherwise there is no existing node with the same name and no node at this location
                        return upstream ? LinkErrorCodes.WarningUpstreamNodeMissing : LinkErrorCodes.WarningDownstreamNodeMissing;
                    }
                }
            }

            return LinkErrorCodes.None;
        }

        internal int GetNodeId(string nodeName, bool activeOnly = true)
        {
            IPoint dummyPoint;
            return GetNodeId(nodeName, out dummyPoint);
        }

        internal int GetNodeId(string nodeName, out IPoint nodePoint, bool activeOnly = true)
        {
            int existingNodeId = -1;
            nodePoint = null;

            if (!string.IsNullOrEmpty(nodeName))
            {
                IFeatureClass nodeClass = this.Configuration.MasterNodesEditClass;

                int nodeIdField = nodeClass.FindField("node_id");
                IQueryFilter query = new QueryFilterClass();
                query.WhereClause = string.Format("node_name = '{0}'", nodeName);
                if (activeOnly)
                    query.WhereClause += " AND status = 'ACT'";
                IFeatureCursor cursor = nodeClass.Search(query, false);
                try
                {
                    IFeature node = cursor.NextFeature();
                    if (node != null)
                    {
                        try
                        {
                            existingNodeId = (int)node.get_Value(nodeIdField);
                            nodePoint = node.ShapeCopy as IPoint;
                        }
                        finally
                        {
                            EmgaatsMaintenanceExtension.ReleaseComObject(node);
                        }
                    }
                }
                finally
                {
                    EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
                }
            }

            return existingNodeId;
        }

        protected override void OnLoad(Stream inStrm)
        {
            LinkToolSettings linkToolSettings = null;
            try
            {
                // NOTE: Do not close or dispose BinaryReader, as this will close the Stream
                BinaryReader reader = new BinaryReader(inStrm);
                int version = reader.ReadInt32();
                
                if (version <= 3)
                {
                    linkToolSettings = new LinkToolSettings();

                    // Version 1-3:
                    // Item 1: link tool append feature class (as FeatureClassName)
                    // Item 2: link tool archive feature class (as FeatureClassName)

                    FeatureClassName appendName = null;
                    PersistenceHelper.Load<FeatureClassName>(inStrm, ref appendName);
                    linkToolSettings.AppendClass = appendName == null ? null : _SafeOpen(appendName) as IFeatureClass;

                    FeatureClassName archiveName = null;
                    PersistenceHelper.Load<FeatureClassName>(inStrm, ref archiveName);
                    linkToolSettings.ArchiveClass = archiveName == null ? null : _SafeOpen(archiveName) as IFeatureClass;


                    // REMAINING ITEMS from version 1 & 2 are now ignored
                    // Version 1-2:
                    // Item 3: link tool dem dataset (as RasterDatasetName)

                    // Version 2 only:
                    // Item 4: link tool configuration file path (string)
                }

                if (_linkToolSettings != null)
                {
                    _linkToolSettings.Dispose();
                }
                _linkToolSettings = linkToolSettings;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.GetType().FullName + ": " + ex.Message);
                _linkToolSettings = null;
            }
        }

        protected override void OnSave(Stream outStrm)
        {
            if (_linkToolSettings != null)
            {
                // NOTE: Do not close or dispose BinaryWriter, as this will close the Stream
                BinaryWriter writer = new BinaryWriter(outStrm);
                int version = 3;
                writer.Write(version);

                // Item 1: link tool append feature class (as FeatureClassName)
                FeatureClassName appendName = _linkToolSettings.AppendClass == null ? null : ((IDataset)_linkToolSettings.AppendClass).FullName as FeatureClassName;
                PersistenceHelper.Save<FeatureClassName>(outStrm, appendName);

                // Item 2: link tool archive feature class (as FeatureClassName)
                FeatureClassName archiveName = _linkToolSettings.ArchiveClass == null ? null : ((IDataset)_linkToolSettings.ArchiveClass).FullName as FeatureClassName;
                PersistenceHelper.Save<FeatureClassName>(outStrm, archiveName);
            }
        }

        private object _SafeOpen(IName datasetName)
        {
            // Returns null if opening dataset fails (e.g. dataset no longer exists)
            object result = null;
            if (datasetName != null)
            {
                try
                {
                    result = datasetName.Open();
                }
                catch (COMException comex)
                {
                    // Do nothing
                    System.Diagnostics.Debug.WriteLine(string.Format("{0}: {1} ({2})", comex.GetType().FullName, comex.Message, comex.ErrorCode));
                }
            }
            return result;
        }

        public static string GetDataFilePath(string fileName)
        {
            string filePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            filePath = System.IO.Path.Combine(filePath, "Data");
            filePath = System.IO.Path.Combine(filePath, fileName);
            return filePath;
        }

        public static int AddField(IFeatureClass featureClass, string fieldName, esriFieldType fieldType, int width)
        {
            IFieldEdit field;
            field = new FieldClass();
            field.Name_2 = fieldName;
            field.DefaultValue_2 = 0.0;
            field.Type_2 = fieldType;
            if (fieldType == esriFieldType.esriFieldTypeString || fieldType == esriFieldType.esriFieldTypeBlob)
                field.Length_2 = width;

            featureClass.AddField(field);
            return featureClass.FindField(fieldName);
        }

        public static int AddField(IFeatureClass featureClass, string fieldName, esriFieldType fieldType)
        {
            return AddField(featureClass, fieldName, fieldType, 50);
        }

        private void _DocumentEvents_CloseDocument()
        {
            _ClearProject();
        }

        private void _DocumentEvents_OpenDocument()
        {
            // Do not clear when opening a document, since it may have a saved setup
        }

        private void _DocumentEvents_NewDocument()
        {
            _ClearProject();
        }

        private void _Events_ActiveViewChanged()
        {
            if (_activeView != null)
            {
                _activeView.SelectionChanged += new IActiveViewEvents_SelectionChangedEventHandler(_ActiveView_SelectionChanged);
                ReleaseComObject(_activeView);
            }

            _activeView = ArcMap.Document.FocusMap as IActiveViewEvents_Event;
            _activeView.SelectionChanged += new IActiveViewEvents_SelectionChangedEventHandler(_ActiveView_SelectionChanged);
        }

        private void _ActiveView_SelectionChanged()
        {
            _dmeSelectionCount = null;
            _linkSelectionCount = null;
        }

        private void _ClearProject()
        {
            // A null setup object indicates this document has not been used for Emgaats editing
            _linkToolSettings = null;
        }

        private List<ILayer> _GetLayers()
        {
            List<ILayer> layers = new List<ILayer>();

            IMap map = null;
            try
            {
                map = ArcMap.Document.FocusMap;
                for (int i = 0; i < map.LayerCount; i++)
                {
                    ILayer pLayer = map.get_Layer(i);
                    if (pLayer is ICompositeLayer)
                        _AddSubLayers((ICompositeLayer)pLayer, layers);
                    else
                        layers.Add(pLayer);
                }
            }
            finally
            {
                EmgaatsMaintenanceExtension.ReleaseComObject(map);
            }

            return layers;
        }

        private void _AddSubLayers(ICompositeLayer compositeLayer, List<ILayer> layers)
        {
            for (int i = 0; i < compositeLayer.Count; i++)
            {
                ILayer pLayer = compositeLayer.get_Layer(i);
                if (pLayer is ICompositeLayer)
                    _AddSubLayers((ICompositeLayer)pLayer, layers);
                else
                    layers.Add(pLayer);
            }
        }
    }
}
